/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.ristolaakso.neuroopt2;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.ParseException;
import java.util.Random;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import netmorph.model.ConfigFile;
import netmorph.model.NetmorphExec;
import neuron.Network;
import neuron.file.NetmorphLoader;
import neuron.file.NeuroLucidaLoader;
import optimization.LinearAlgebra;
import optimization.LinearAlgebra.StochasticFunction;
import optimization.LinearAlgebra.VectorFunction;
import optimization.RetrospectiveApproximation;

/**
 *
 * @author Risto Laakso <risto.laakso@gmail.com>
 */
public class Main {

	public static final String BASAL_B = "all_dendrites.B_inf";
	public static final String BASAL_E = "all_dendrites.E";
	public static final String BASAL_S = "all_dendrites.S";
	public static final String BASAL_erim = "all_dendrites.eri.PDF.mean";
	public static final String BASAL_eris = "all_dendrites.eri.PDF.std";
	public static final String BASAL_tsemm = "all_dendrites.tsem.branch.PDF.mean";
	public static final String BASAL_tsems = "all_dendrites.tsem.branch.PDF.std";
	public static final String BASAL_anglemin = "all_dendrites.veeranglemin";
	public static final String BASAL_anglemax = "all_dendrites.veeranglemax";
	public static final String BASAL_histpow = "all_dendrites.history_power";
	public static final String BASAL_bamm = "all_dendrites.bam.PDF.mean";
	public static final String BASAL_bams = "all_dendrites.bam.PDF.std";
	public static final String BASAL_turnsep = "all_dendrites.turn_separation";
	public static final String BASAL_turnrate = "all_dendrites.turn_rate";

	public static void analyzeData(String path) throws FileNotFoundException, IOException, ParseException
	{
		Network data = new Network();
		File dir = new File(path);
		for (File f : dir.listFiles()) {
			System.out.println(f);
			Network net = NeuroLucidaLoader.load(f);
			data.add(net.get(0));
		}
		System.gc();
		Logger.getLogger("com.ristolaakso").logp(Level.INFO, Thread.currentThread().getStackTrace()[0].getClassName(), Thread.currentThread().getStackTrace()[0].getMethodName(), "Used memory: %d",	Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());

		FeatureVector fvData = new FeatureVector(data);
		System.out.println(fvData.toString());

	}
	
	private static class NetmorphThread extends Thread {

		public FeatureVector result = null;
		public int N = 20;
		public long seed = 1234;
		
		@Override
		public void run() {
		
			try {
				config.put("V.pyramidal", ""+N);
				config.put("randomseed", ""+seed);
				NetmorphExec netmorph = new NetmorphExec(config, "netopt-"+ Thread.currentThread().getName());
				netmorph.configure();
				netmorph.start();
				Network net = NetmorphLoader.load(netmorph.getNeurons());
				result = new FeatureVector(net);
				Logger.getLogger("com.ristolaakso").logp(Level.INFO, Thread.currentThread().getStackTrace()[0].getClassName(), Thread.currentThread().getStackTrace()[0].getMethodName(), "Model features: " + result);
			} catch (Exception e) {
				throw new RuntimeException("Internal error: ", e);
			}
		}
		
	}

	static ConfigFile config = new ConfigFile();
	public static FeatureVector[] runNetmorph(int N, int instances, long randSeed)
	{
		Random rnd = new Random();
		rnd.setSeed(randSeed);

		FeatureVector[] res = new FeatureVector[instances];
		NetmorphThread[] threads = new NetmorphThread[instances];
		for (int i = 0; i < instances; i++) {
			threads[i] = new NetmorphThread();
			threads[i].N = N;
			threads[i].setName("worker-" + i);
			threads[i].seed = rnd.nextLong();
			threads[i].start();
		}
		for (int i = 0; i < instances; i++) {
			try {
				threads[i].join();
				res[i] = threads[i].result;
			} catch (InterruptedException ex) {
				Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Thread join interrupted!", ex);
			}
		}
		return res;
	}

	public static int PHASE = 1;

	static FeatureVector fvBCETV = new FeatureVector(new double[] {
			4.965853658536585, 0.1407031562392796, 20.13972823878816, 0.6123708746088177, 1.3938623118973545,
			0.5862068965517241, 3.206896551724138, 5.758620689655173, 6.137931034482759, 5.482758620689655,
			4.689655172413793, 4.448275862068965, 4.206896551724138, 4.379310344827586, 4.724137931034483,
			3.2413793103448274, 2.8620689655172415, 2.586206896551724, 2.5172413793103448, 1.9655172413793103,
			2.0689655172413794, 1.206896551724138, 0.896551724137931, 0.41379310344827586, 0.5517241379310345
		});


	static void configModel(double[] p)
	{
		switch(PHASE) {
			case 1: // branching
				config.put(BASAL_B,String.format("%.2f", p[0]));
				config.put(BASAL_E,String.format("%.2f", p[1]));
				break;
			case 2: // asym
				config.put(BASAL_S,String.format("%.2f", p[0]));
				break;
			case 3:
				config.put(BASAL_erim,String.format("%.2f", p[0]));
				config.put(BASAL_eris, String.format("%.2f", p[1]));
				break;
			case 4:
				config.put(BASAL_tsemm,String.format("%.2f", p[0]));
				config.put(BASAL_tsems,String.format("%.2f", p[1]));
				break;
			case 5:
				config.put(BASAL_anglemin,String.format("%.2f", p[0]));
				config.put(BASAL_anglemax,String.format("%.2f", p[1]));
				break;
			case 6:
				config.put(BASAL_bamm,String.format("%.2f", p[0]));
				config.put(BASAL_bams,String.format("%.2f", p[1]));
				break;
			case 7:
				config.put(BASAL_turnsep,String.format("%.2f", p[0]));
				config.put(BASAL_turnrate,String.format("%.2f", p[1]));
				break;
			default:
				throw new UnsupportedOperationException("PHASE not implemented");
		}
	}

	/**
	 * @param args
	 * @throws ParseException
	 * @throws IOException
	 * @throws FileNotFoundException
	 */
	public static void main(String[] args) throws FileNotFoundException,
			IOException, ParseException
	{

		ConsoleHandler ch = new ConsoleHandler();
		ch.setLevel(Level.ALL);
		FileHandler fh = new FileHandler("netopt2_%g.log");
		fh.setLevel(Level.ALL);
		Logger.getLogger("com.ristolaakso").addHandler(ch);
		Logger.getLogger("com.ristolaakso").addHandler(fh);

//			analyzeData("data/bcetv-krieger/");

		StochasticFunction f = new StochasticFunction() {

			@Override
			public double evaluate(double[] p, int N, long randSeed) {

				configModel(p);


				int cores = Runtime.getRuntime().availableProcessors();

				double[] mean = new double[25];
				int k = 0, times = (int)Math.ceil(N/20.);
				while (times > 0) {
					int nSim = 8;//Math.min(times, cores);
					FeatureVector[] res = runNetmorph(20, nSim, randSeed);
					for (FeatureVector fv : res) {
						LinearAlgebra.vAdd(mean, fv.feat);
						k++;
					}
					times -= nSim;
				}
				LinearAlgebra.vMul(1.0/k, mean);
				FeatureVector fv = new FeatureVector(mean);
				if (fv.hasNAN()) return Double.MAX_VALUE;
				double dist = fvBCETV.distanceN2(fv);
				Logger.getLogger("com.ristolaakso").logp(Level.INFO, Thread.currentThread().getStackTrace()[0].getClassName(), Thread.currentThread().getStackTrace()[0].getMethodName(),
						  "Distance at : " + LinearAlgebra.vPrint(p) + " -> " + dist);
				return dist;
			}
		};

		VectorFunction cons = new LinearAlgebra.VectorFunction() {

				@Override
				public double[] evaluate(double[] p) {
					for (int i = 0; i < p.length; i++) {
						if (p[i] < 0) {
							p[i] = 0.01;
						}
					}
					return p;
				}

				@Override
				public int yDim() {
					return 1;
				}
			};


		for (int phase = 1; phase <= 7; phase++) {

			PHASE = phase;

			Logger.getLogger("com.ristolaakso").logp(Level.INFO, Thread.currentThread().getStackTrace()[0].getClassName(), Thread.currentThread().getStackTrace()[0].getMethodName(),
					  "PHASE is now " + PHASE);

			// get params per PHASE
			double x0[] = null, h[] = null;
			switch(PHASE) {
				case 1:
					x0 = new double[]{ config.getD(BASAL_B), config.getD(BASAL_E)};
					h = new double[]{ 0.2, 0.1 };
					break;
				case 2:
					x0 = new double[]{ config.getD(BASAL_S) };
					h = new double[]{ 0.1 };
					break;
				case 3:
					x0 = new double[]{ config.getD(BASAL_erim), config.getD(BASAL_eris)};
					h = new double[]{ 0.2, 0.1 };
					break;
				case 4:
					x0 = new double[]{ config.getD(BASAL_tsemm), config.getD(BASAL_tsems)};
					h = new double[]{ 0.2, 0.1 };
					break;
				case 5:
					x0 = new double[]{ config.getD(BASAL_anglemin), config.getD(BASAL_anglemax)};
					h = new double[]{ 0.1, 0.1 };
					break;
				case 6:
					x0 = new double[]{ config.getD(BASAL_bamm), config.getD(BASAL_bams)};
					h = new double[]{ 0.2, 0.1 };
					break;
				case 7:
					x0 = new double[]{ config.getD(BASAL_turnsep), config.getD(BASAL_turnrate)};
					h = new double[]{ 0.1, 0.1 };
					break;
			}

			// optimize
			RetrospectiveApproximation ra = new RetrospectiveApproximation(f, cons, x0, h);
			ra.search(3);
			double[] p = ra.estimateRoot();
			Logger.getLogger("com.ristolaakso").logp(Level.INFO, Thread.currentThread().getStackTrace()[0].getClassName(), Thread.currentThread().getStackTrace()[0].getMethodName(),
				"Root estimate is : " + LinearAlgebra.vPrint(p));

			// set apxRoot to model
			configModel(p);

			File out = new File("config_phase_" + PHASE + ".txt");
			config.writeFile(out);
		}

			// iterate
	}
}
